// Copyright (c) 2011 Blue Onion Software, All rights reserved
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;

namespace BlueOnionSoftware.Bloget
{
    internal class Opml
    {
        public Opml()
        {
            Outlines = new OpmlOutlineCollection();
            OwnerEmail = string.Empty;
            OwnerName = string.Empty;
            DateModified = DateTime.Now;
            Title = string.Empty;
            DateCreated = DateTime.Now;
        }

        internal string Title { get; private set; }

        internal DateTime DateCreated { get; private set; }

        internal DateTime DateModified { get; set; }

        internal string OwnerName { get; private set; }

        internal string OwnerEmail { get; private set; }

        internal OpmlOutlineCollection Outlines { get; private set; }

        internal void AddOutline(OpmlOutline item)
        {
            Outlines.Add(item);
        }

        internal void AddOutline(string titleArg, string descriptionArg)
        {
            AddOutline(titleArg, descriptionArg, null, null, null);
        }

        internal void AddOutline(string titleArg, string descriptionArg, Uri htmlUrlArg, Uri xmlUrlArg,
                                 OpmlOutlineCollection outlinesArg)
        {
            var item = new OpmlOutline
            {
                Title = titleArg,
                Description = descriptionArg,
                HtmlUrl = htmlUrlArg,
                XmlUrl = xmlUrlArg
            };
            item.AddOutlines(outlinesArg);
            Outlines.Add(item);
        }

        internal void Save(Stream stream)
        {
            Throw.IfNull(stream, "stream");

            var opmlDoc = new XmlDocument();
            var opml = opmlDoc.CreateElement("opml");
            opml.SetAttribute("version", "1.1");
            opmlDoc.AppendChild(opml);

            var headElement = opmlDoc.CreateElement("head");
            opml.AppendChild(headElement);

            var titleElement = opmlDoc.CreateElement("title");
            titleElement.InnerText = Title;
            headElement.AppendChild(titleElement);

            var dateCreatedElement = opmlDoc.CreateElement("dateCreated");
            dateCreatedElement.InnerText = DateCreated.ToString("r", CultureInfo.InvariantCulture);
            headElement.AppendChild(dateCreatedElement);

            var dateModifiedElement = opmlDoc.CreateElement("dateModified");
            dateModifiedElement.InnerText = DateModified.ToString("r", CultureInfo.InvariantCulture);
            headElement.AppendChild(dateModifiedElement);

            var ownerEmailElement = opmlDoc.CreateElement("ownerEmail");
            ownerEmailElement.InnerText = OwnerEmail;
            headElement.AppendChild(ownerEmailElement);

            var ownerNameElement = opmlDoc.CreateElement("ownerName");
            ownerNameElement.InnerText = OwnerName;
            headElement.AppendChild(ownerNameElement);

            var opmlBodyElement = opmlDoc.CreateElement("body");
            opml.AppendChild(opmlBodyElement);

            foreach (var outline in Outlines)
            {
                opmlBodyElement.AppendChild(OutlineToXml(opmlDoc, outline));
            }

            opmlDoc.Save(stream);
        }

        private static XmlElement OutlineToXml(XmlDocument opmlDoc, OpmlOutline outline)
        {
            var outlineNode = opmlDoc.CreateElement("outline");

            if (String.IsNullOrEmpty(outline.Title) == false)
            {
                outlineNode.SetAttribute("title", outline.Title);
            }

            if (String.IsNullOrEmpty(outline.Text) == false)
            {
                outlineNode.SetAttribute("text", outline.Text);
            }

            if (String.IsNullOrEmpty(outline.Description) == false)
            {
                outlineNode.SetAttribute("description", outline.Description);
            }

            if (String.IsNullOrEmpty(outline.Type) == false)
            {
                outlineNode.SetAttribute("type", outline.Type);
            }

            if (outline.HtmlUrl != null)
            {
                outlineNode.SetAttribute("htmlUrl", outline.HtmlUrl.ToString());
            }

            if (outline.XmlUrl != null)
            {
                outlineNode.SetAttribute("xmlUrl", outline.XmlUrl.ToString());
            }

            foreach (var childOutline in outline.Outlines)
            {
                outlineNode.AppendChild(OutlineToXml(opmlDoc, childOutline));
            }

            return outlineNode;
        }

        internal static Opml Parse(Stream stream)
        {
            Throw.IfNull(stream, "stream");

            var opmlDoc = new XmlDocument();
            opmlDoc.Load(stream);

            // Parse headNode
            var headNode = opmlDoc.GetElementsByTagName("head")[0];
            var titleNode = headNode.SelectSingleNode("./title");
            var dateCreatedNode = headNode.SelectSingleNode("./dateCreated");
            var dateModifiedNode = headNode.SelectSingleNode("./dateModified");
            var ownerNameNode = headNode.SelectSingleNode("./ownerName");
            var ownerEmailNode = headNode.SelectSingleNode("./ownerEmail");

            var opml = new Opml();

            if (titleNode != null)
            {
                opml.Title = titleNode.InnerText;
            }

            if (dateCreatedNode != null)
            {
                DateTime created;

                if (DateTime.TryParse(dateCreatedNode.InnerText, out created))
                {
                    opml.DateCreated = created;
                }
            }

            if (dateModifiedNode != null)
            {
                DateTime modified;

                if (DateTime.TryParse(dateModifiedNode.InnerText, out modified))
                {
                    opml.DateModified = modified;
                }
            }

            if (ownerNameNode != null)
            {
                opml.OwnerName = ownerNameNode.InnerText;
            }

            if (ownerEmailNode != null)
            {
                opml.OwnerEmail = ownerEmailNode.InnerText;
            }

            // Parse body
            var nodeList = opmlDoc.GetElementsByTagName("body");

            if (nodeList[0] != null)
            {
                var body = nodeList[0];
                var outlineList = body.SelectNodes("./outline");

                foreach (XmlElement outline in outlineList)
                {
                    opml.Outlines.Add(ParseContent(outline));
                }
            }

            return opml;
        }

        private static OpmlOutline ParseContent(XmlElement xmlNode)
        {
            var outline = new OpmlOutline
            {
                Title = xmlNode.GetAttribute("title"),
                Text = xmlNode.GetAttribute("text"),
                Description = xmlNode.GetAttribute("description"),
                Type = xmlNode.GetAttribute("type")
            };

            Uri htmlUrl;
            if (Uri.TryCreate(xmlNode.GetAttribute("htmlUrl"), UriKind.RelativeOrAbsolute, out htmlUrl))
            {
                outline.HtmlUrl = htmlUrl;
            }

            Uri xmlUrl;
            if (Uri.TryCreate(xmlNode.GetAttribute("xmlUrl"), UriKind.RelativeOrAbsolute, out xmlUrl))
            {
                outline.XmlUrl = xmlUrl;
            }

            if (xmlNode.HasChildNodes)
            {
                foreach (XmlElement childNode in xmlNode.SelectNodes("./outline"))
                {
                    outline.AddOutline(ParseContent(childNode));
                }
            }

            return outline;
        }
    }

    internal class OpmlOutlineCollection : List<OpmlOutline>
    {
    }

    public class OpmlOutline
    {
        private readonly OpmlOutlineCollection _outlines = new OpmlOutlineCollection();
        private string _title = string.Empty;
        private string _description = string.Empty;
        private string _text = string.Empty;
        private string _type = string.Empty;

        internal string Description
        {
            get { return _description; }
            set { _description = value; }
        }

        internal string Title
        {
            get { return _title; }
            set { _title = value; }
        }

        internal string Type
        {
            get { return _type; }
            set { _type = value; }
        }

        internal string Text
        {
            get { return _text; }
            set { _text = value; }
        }

        internal Uri HtmlUrl { get; set; }

        internal Uri XmlUrl { get; set; }

        internal OpmlOutlineCollection Outlines
        {
            get { return _outlines; }
        }

        internal void ClearOutlines()
        {
            _outlines.Clear();
        }

        internal void AddOutline(OpmlOutline outline)
        {
            _outlines.Add(outline);
        }

        internal void AddOutlines(IEnumerable<OpmlOutline> outlineCollection)
        {
            _outlines.AddRange(outlineCollection);
        }
    }
}